import 'package:winkai/db/db_ext.dart';
import 'package:winkai/entity/msg_constant.dart';
import 'package:winkai/helper/user_helper.dart';

import '../database.dart';
import 'package:drift/drift.dart';

import '../tables/msg_tb.dart';
import '../tables/ses_tb.dart';
import '../tables/user_tb.dart';

part 'ses_dao.g.dart';

class SesInfo {
  /// 会话
  SesTbData session;

  /// 会话关联的用户
  UserTbData? user;

  /// 会话关联的最新消息
  MsgTbData? message;

  /// 最新消息关联的用户
  UserTbData? msgUser;

  /// at通知技术
  int unreadCount;

  SesInfo({
    required this.session,
    required this.unreadCount,
    this.msgUser,
    this.user,
    this.message,
  });
}

@DriftAccessor(tables: [
  MsgTb,
  SesTb,
  UserTb,
])
class SesDao extends DatabaseAccessor<RootDatabase> with _$SesDaoMixin {
  SesDao(super.attachedDatabase);

  Expression<bool> _expr({String? id, bool? visible}) {
    var items = <Expression<bool>>[sesTb.accountExpr];
    if (id != null) {
      items.add(sesTb.id.equals(id));
    }
    if (visible != null) {
      items.add(sesTb.visible.equals(visible));
    }
    return exprFactory(items.iterator);
  }

  Selectable<SesInfo> _selectJoins(
      SimpleSelectStatement<$SesTbTable, SesTbData> s) {
    final sesUser = alias(userTb, 'sesUser');
    final msgUser = alias(userTb, 'msgUser');

    return (s.join([
      leftOuterJoin(
          sesUser, sesUser.accountExpr & sesUser.id.equalsExp(sesTb.userId)),
      leftOuterJoin(
          msgTb, msgTb.accountExpr & msgTb.id.equalsExp(sesTb.lastMsgId)),
      leftOuterJoin(
          msgUser, msgUser.accountExpr & msgUser.id.equalsExp(msgTb.userId)),
    ])
          ..groupBy([sesTb.id]))
        .map((row) {
      var sessionData = row.readTable(sesTb);
      return SesInfo(
        session: sessionData,
        unreadCount: sessionData.unReadNum,
        user: row.readTableOrNull(sesUser),
        message: row.readTableOrNull(msgTb),
        msgUser: row.readTableOrNull(msgUser),
      );
    });
  }

  Future<List<SesInfo>> selectAll(String accountId, [bool? visible]) {
    return _selectJoins((select(sesTb)
          ..where((tbl) => _expr(visible: visible) & tbl.lastMsgId.isNotNull())
          ..orderBy([
            (tbl) => OrderingTerm.desc(tbl.isTop),
            (tbl) => OrderingTerm.desc(tbl.updateTime)
          ])))
        .get();
  }

  Future<SesInfo?> selectSingleOrNull(String id) {
    return _selectJoins((select(sesTb)..where((tbl) => _expr(id: id))))
        .getSingleOrNull();
  }

  Future<int> selectUnReadUnmSum() {
    return customSelect(
      'SELECT SUM(un_read_num) AS c FROM session WHERE account_id = ?',
      variables: [Variable.withString(UserHelper().userId)],
      readsFrom: {sesTb},
    ).map((row) => row.read<int>('c')).getSingle();
  }

  Future<int> insertOrUpdate({
    required String accountId,
    required String id,
    int? lastMsgId,
    String? userId,
    SesType sessionType = SesType.single,
    int unReadNum = 0,
    bool visible = true,
    DateTime? updateTime,
  }) {
    return into(sesTb).insertOnConflictUpdate(SesTbCompanion.insert(
      accountId: accountId,
      id: id,
      lastMsgId: lastMsgId != null ? Value(lastMsgId) : const Value.absent(),
      userId: Value(userId),
      sessionType: sessionType,
      unReadNum: Value(unReadNum),
      visible: Value(visible),
      updateTime: updateTime != null ? Value(updateTime) : const Value.absent(),
    ));
  }

  Future<int> updateUnReadUnmAndLastMsgId(
    String id,
    int unReadNum,
    int lastMsgId,
  ) {
    return (update(sesTb)..where((tbl) => tbl.id.equals(id)))
        .write(SesTbCompanion(
      unReadNum: Value(unReadNum),
      lastMsgId: Value(lastMsgId),
      updateTime: Value(DateTime.now()),
    ));
  }

  Future<int> updateLastMsgId(
    String id,
    int lastMsgId,
  ) {
    return (update(sesTb)..where((tbl) => tbl.id.equals(id)))
        .write(SesTbCompanion(
      lastMsgId: Value(lastMsgId),
      updateTime: Value(DateTime.now()),
    ));
  }

  Future<int> updateUnReadNum(String id, int unReadNum) {
    return (update(sesTb)..where((tbl) => tbl.id.equals(id)))
        .write(SesTbCompanion(
      unReadNum: Value(unReadNum),
      updateTime: Value(DateTime.now()),
    ));
  }

  Future<int> resetUnReadNum(String id) {
    return (update(sesTb)..where((tbl) => tbl.id.equals(id)))
        .write(const SesTbCompanion(unReadNum: Value(0)));
  }

  Future<int> deleteById(String id) async {
    return (delete(sesTb)..where((tbl) => tbl.id.equals(id))).go();
  }

  Future<int> deleteByCurrentAccount() {
    return (delete(sesTb)..where((tbl) => tbl.accountExpr)).go();
  }
}
